/************************************************
 * Author       :   XXY
 * Blog         :   https://www.cnblogs.com/jzyl
 * Email        :   1047185209@QQ.com
 * FileName     :   AssetBundlesManager.cs
 * CreateData   :   2023/4/28 9:33:18
 * UnityVersion :   2020.3.33f1c2
 * Description  :   AB 资源管理类
************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine.Networking;
using Newtonsoft.Json;
using System.IO;
using Cysharp.Threading.Tasks;
using System.Threading;

namespace Main
{
    /// <summary>
    /// AB 资源管理类
    /// </summary>
    public class AssetBundlesManager : Singleton<AssetBundlesManager>
    {
        #region 字段
        /// <summary>
        /// manifest文件
        /// </summary>
        private AssetBundleManifest manifest;
        /// <summary>
        /// AB包文件夹路径
        /// </summary>
        private string abPath = "";
        /// <summary>
        ///  是否取消AB包下载
        /// </summary>
        private bool isCancelABDownLoad;
        /// <summary>
        /// 取消任务指令
        /// </summary>
        CancellationTokenSource cts = new CancellationTokenSource();
        #endregion
        #region 属性
        /// <summary>
        /// 当前AB包地址（用于加载AB包,加载AB包之前需先设置AB包本地地址）
        /// </summary>
        public string ABLocalPath
        {
            get
            {
                return abPath;
            }
            set
            {
                if (Directory.Exists(value) && abPath != value)
                {
                    abPath = value;
                    manifest = GetManifest(abPath + "AssetBundle");
                }
            }
        }
        #endregion
        #region 加载AB包
        /// <summary>
        /// 获取清单文件
        /// </summary>

        private AssetBundleManifest GetManifest(string manifestFilePath)
        {
            // 加载包含manifest文件的AssetBundle
            AssetBundle manifestBundle = AssetBundle.LoadFromFile(manifestFilePath);

            // 获取manifest文件
            AssetBundleManifest manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

            // 卸载AB
            manifestBundle.Unload(false);

            return manifest;
        }
        /// <summary>
        /// 通过名称加载AB资源包
        /// </summary>
        /// <param name="abName"></param>
        /// <returns></returns>
        public AssetBundle LoadAB(string abName)
        {
            string abFile = abPath + abName;
            var ab = GetLoadedAB(abName);
            if (ab != null)
            {
                return ab;
            }
            else
            {
                if (File.Exists(abFile))
                {
                    string[] dependencies = manifest.GetAllDependencies(abName);
                    foreach (string dependency in dependencies)
                    {
                        if (GetLoadedAB(dependency) == null)
                        {
                            AssetBundle.LoadFromFile(abPath + dependency);
                        }
                    }
                    return AssetBundle.LoadFromFile(abFile);
                }
                else
                {
                    LogUtillity.Error("AB包不存在:\n" + abFile);
                    return null;
                }
            }
        }
        /// <summary>
        /// 异步加载AB包资源
        /// </summary>
        /// <param name="name">要加载AB包名称</param>
        /// <param name="CompleteCallback">加载完成后的回调,AssetBundle 为null表示 取消 操作</param>
        /// <param name="Progress">加载进度（AB包名称，AB包加载进度）</param>
        public async UniTask<AssetBundle> LoadABAsync(string abName, Action<AssetBundle> CompleteCallback = null, Action<string, float> Progress = null)
        {
            isCancelABDownLoad = false;
            cts = new CancellationTokenSource();
            string abFile = abPath + abName;
            await UniTask.Yield();
            var ab = GetLoadedAB(abName);
            if (ab != null)
            {
                CompleteCallback?.Invoke(ab);
                return ab;
            }
            else
            {
                if (File.Exists(abFile))
                {
                    string[] dependencies = manifest.GetAllDependencies(abName);
                    foreach (string dependency in dependencies)
                    {
                        if (GetLoadedAB(dependency) == null)
                        {
                            AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(abPath + dependency);
                            _ = request.WithCancellation(cts.Token);
                            while (!request.isDone)
                            {
                                await UniTask.Yield();
                                Progress?.Invoke(dependency, request.progress);
                                if (isCancelABDownLoad)
                                {
                                    CompleteCallback?.Invoke(null);
                                    return null;
                                }
                            }
                        }
                    }
                    AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(abFile);
                    _ = abcr.WithCancellation(cts.Token);
                    while (!abcr.isDone)
                    {
                        await UniTask.Yield();
                        Progress?.Invoke(abName, abcr.progress);
                        if (isCancelABDownLoad)
                        {
                            CompleteCallback?.Invoke(null);
                            return null;
                        }
                    }
                    CompleteCallback?.Invoke(abcr.assetBundle);
                    return abcr.assetBundle;
                }
                else
                {
                    LogUtillity.Error("AB包不存在:\n" + abFile);
                    CompleteCallback?.Invoke(null);
                }
            }
            return null;
        }
        /// <summary>
        /// 卸载所有AB包
        /// </summary>
        /// <param name="isReleaseRes">是否释放实例对象</param>
        public void UnloadAllAssetBundles(bool isReleaseRes = false)
        {
            AssetBundle.UnloadAllAssetBundles(isReleaseRes);
        }
        #endregion
        #region 下载 
        /// <summary>
        /// downloadHandler.text﻿ 您在左大括号之前看到的字符是字节顺序标记 （BOM）。它是一个特殊字符，可以添加到文件的开头以指示其编码类型。在这种情况下，BOM 可能会导致 JSON 分析器失败
        /// </summary>
        private string RemoveBOM(string json)
        {
            if (json.StartsWith("\uFEFF"))
            {
                json = json.Substring(1);
            }
            return json;
        }
        /// <summary>
        /// 获取已加载的AB包
        /// </summary>
        /// <param name="abName"></param>
        /// <returns></returns>
        private AssetBundle GetLoadedAB(string abName)
        {
            AssetBundle ab = AssetBundle.GetAllLoadedAssetBundles().ToList().FirstOrDefault(ab => ab.name == abName);
            return ab;
        }
        /// <summary>
        /// 获取要下载的AB包列表
        /// </summary>
        /// <param name="url">AB包列表地址</param>
        /// <returns>更新或者要下载的AB包列表</returns>
        public async UniTask<List<ABInfo>> GetDownLoadABs(string url)
        {
            var abJson = (await UnityWebRequest.Get(url + "/MD5.json").SendWebRequest()).downloadHandler.text;
            abJson = RemoveBOM(abJson);
            ABJson json = JsonConvert.DeserializeObject<ABJson>(abJson);
            // 本地AB文件夹
            string abPathDir = PathConfig.LocalABStorePath + $"{json.projectName}/{json.version}/";
            // 判断路径是否存在
            if (!Directory.Exists(abPathDir))
            {
                Directory.CreateDirectory(abPathDir);
            }
            // 获取要下载的AB包列表
            List<ABInfo> abLists = new List<ABInfo>();
            // 要下载AB包的大小
            long absSize = 0;
            foreach (var abInfo in json.abArray)
            {
                string filePath = $"{abPathDir}/{abInfo.Key}";
                if (!File.Exists(filePath) || FileUtility.GetFileMd5(filePath) != abInfo.Value.md5)
                {
                    absSize += abInfo.Value.size;
                    abLists.Add(abInfo.Value);
                }
            }
            return abLists;
        }
        /// <summary>
        /// 下载AB包集合
        /// </summary>
        /// <param name="url">AB包下载链接</param>
        /// <param name="DownLoadABCallback">开始下载 AB包回调</param>
        /// <param name="ProgressCallback">下载进度回调（第一个参数为已下载字节，第二参数为总字节大小）</param>
        /// <param name="CompleteCallback">结束回调(返回值表示是否下载完成，true:下载成功(返回本地包路径) ,flase:表示中断下载(中断原因))</param>
        /// <returns>本地AB包地址</returns>

        public async UniTaskVoid DownLoadABs(string url, Action<string> DownLoadABCallback = null, Action<long, long> ProgressCallback = null, Action<bool, string> CompleteCallback = null)
        {
            await UniTask.Yield();
            isCancelABDownLoad = false;
            List<ABInfo> abs = new List<ABInfo>();
            long totalSize = 0;         // 总AB大小
            long downloadedSize = 0;    // 已下载字节大小
            string abDir = "";
            try
            {

                #region 获取 AB包列表 信息
                UnityWebRequest webRequest = UnityWebRequest.Get(url + "/MD5.json");
                webRequest.SetRequestHeader("Content-Type", "application/json; charset=UTF-8");
                var abJson = (await webRequest.SendWebRequest()).downloadHandler.text;
                abJson = RemoveBOM(abJson);
                ABJson json = JsonConvert.DeserializeObject<ABJson>(abJson);
                // 本地AB文件夹
                abDir = PathConfig.LocalABStorePath + $"{json.projectName}/{json.version}/";
                // 判断路径是否存在
                if (!Directory.Exists(abDir))
                {
                    Directory.CreateDirectory(abDir);
                }
                foreach (var abInfo in json.abArray)
                {
                    string filePath = $"{abDir}/{abInfo.Key}";
                    if (!File.Exists(filePath) || FileUtility.GetFileMd5(filePath) != abInfo.Value.md5)
                    {
                        totalSize += abInfo.Value.size;
                        abs.Add(abInfo.Value);
                    }
                }
                #endregion

                for (int i = 0; i < abs.Count; i++)
                {
                    string abName = abs[i].name;
                    DownLoadABCallback?.Invoke(abName);
                    string filePath = abDir + abName;
                    string abUrl = $"{url}/{abName}";
                    using (UnityWebRequest web = UnityWebRequest.Get(abUrl))
                    {
                        web.downloadHandler = new DownloadHandlerFile(filePath);
                        _ = web.SendWebRequest();
                        while (!web.isDone)
                        {
                            ProgressCallback?.Invoke(downloadedSize + (long)web.downloadedBytes, totalSize);
                            await UniTask.Yield();
                            if (isCancelABDownLoad)
                            {
                                web.Abort();
                                CompleteCallback?.Invoke(false, "主动取消下载\n" + abDir);
                                return;
                            }
                        }
                        downloadedSize += abs[i].size;
                    }
                }
                CompleteCallback?.Invoke(true, abDir);
            }
            catch (Exception ex)
            {
                CompleteCallback?.Invoke(false, ex.Message);
            }
        }

        /// <summary>
        /// 取消下载AB包
        /// </summary>
        public void Cancel()
        {
            // 取消剩余任务
            isCancelABDownLoad = true;
            cts.Cancel();
        }
        #endregion
    }







































































    /// <summary>
    /// AB Json 类
    /// </summary>
    [Serializable]
    public class ABJson
    {
        /// <summary>
        /// 项目名称
        /// </summary>
        public string projectName;
        /// <summary>
        /// 项目版本
        /// </summary>
        public string version;
        /// <summary>
        /// AB包总数
        /// </summary>
        public int abTotal;
        /// <summary>
        /// AB包总大小
        /// </summary>
        public long abSize;
        /// <summary>
        /// 构建平台
        /// </summary>
        public string buildPlatform;
        /// <summary>
        /// 所有AB包
        /// </summary>
        [SerializeField]
        public Dictionary<string, ABInfo> abArray = new Dictionary<string, ABInfo>();
    }
    /// <summary>
    /// AB 包信息
    /// </summary>
    [Serializable]
    public class ABInfo
    {
        /// <summary>
        /// AB包名称
        /// </summary>
        public string name;
        /// <summary>
        /// AB包MD5校验
        /// </summary>
        public string md5;
        /// <summary>
        /// AB包大小
        /// </summary>
        public long size;
    }
}